#region
/* Adoor.NET - Object/Relational mapping framework leveraging ADO.NET`s powerful disconnected mode.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *  
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *  
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */ 
#endregion

using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Xml;
using Adoor.Expression;
using Adoor.Expression.AST;
using Adoor.Object.Disconnected;
using Adoor.Object.Domain;
using Adoor.Object.Entity;
using Adoor.Object.Query;
using Adoor.Relational;

namespace Adoor.Object.Relational
{	
	public abstract class RelationalEntityFactory<TEntity> : IService, IEntityLoader, IEntityMappingStrategyProvider
	{
		public RelationalEntityFactory(EntityData entityData, XmlNode metadata, XmlNamespaceManager nsmgr)
		{
			this.entityData = entityData;
			this.mappingStrategy = CreateMappingStrategy();

			this.EntityData.RegisterService(typeof(IService), this);
			this.EntityData.RegisterService(typeof(IEntityMappingStrategyProvider), this);
			this.EntityData.RegisterService(typeof(IEntityLoader), this);
		}

		public void Initialize()
		{
			foreach (PropertyData propertyData in this.EntityData.PropertyInfos.Values)
				this.RegisterPropertyData(propertyData.Name, propertyData);

			foreach (ReferenceData referenceData in this.EntityData.ReferenceInfos.Values)
				this.RegisterReferenceData(referenceData.TargetRole, referenceData);

			IKeyMappingStrategy keyMappingStrategy = (IKeyMappingStrategy)this.EntityData.OidData.GetService(typeof(IKeyMappingStrategy));
			keyMappingStrategy.InsertKeyColumns(this.dataColumns);
		}

		protected virtual EntityMappingStrategy CreateMappingStrategy()
		{
			return new EntityMappingStrategy(this);
		}

		public IEntityMappingStrategy GetMappingStrategy(MappingStrategyEnum ms)
		{
			return this.mappingStrategy;
		}

		private EntityData entityData;
		public EntityData EntityData
		{
			get { return this.entityData; }
		}

		public IKeyMappingStrategy KeyMappingStrategy
		{
			get { return (IKeyMappingStrategy)this.EntityData.OidData.GetService(typeof(IKeyMappingStrategy)); }
		}

		public ICollection<DataColumn> DataColumns // TODO: remove from here => MappingStrategy
		{
			get { return this.dataColumns; }
		}

		public void RegisterPropertyData(string name, PropertyData propertyData)
		{
			IPropertyMapping mapping = (IPropertyMapping)propertyData.GetService(typeof(IPropertyMapping));
			mapping.InsertDataColumn(this.dataColumns);
		}

		public void RegisterReferenceData(string role, ReferenceData referenceData)
		{
			IReferenceMapping mapping = (IReferenceMapping)referenceData.GetService(typeof(IReferenceMapping));
			mapping.InsertDataColumn(this.dataColumns);
		}

		public virtual RelationalQuery ProperInstancesQuery
		{
			get { return null; }
		}

        protected class EntityMappingStrategy : IEntityMappingStrategy
		{
            public EntityMappingStrategy(RelationalEntityFactory<TEntity> factory)
			{
				this.entityFactory = factory;
			}

            private RelationalEntityFactory<TEntity> entityFactory;
			public EntityData EntityData
			{
				get { return this.entityFactory.EntityData; }
			}

			private RelationalQuery relationalQuery;
			public virtual RelationalQuery AllInstancesQuery
			{
				get
				{
					//if (this.relationalQuery == null)
				//{
					this.relationalQuery = this.entityFactory.ProperInstancesQuery;
					foreach(EntityData childData in this.entityFactory.EntityData.ChildrenInfo) 
					{
						IEntityMappingStrategyProvider provider = (IEntityMappingStrategyProvider)childData.GetService(typeof(IEntityMappingStrategyProvider));
						IEntityMappingStrategy mappingStrategy = provider.GetMappingStrategy(MappingStrategyEnum.Remote2);
						SelectExpression[] relationalColumns = new SelectExpression[this.entityFactory.DataColumns.Count];
						int i = 0;
						foreach(DataColumn dc in this.entityFactory.DataColumns)
						{
							string thisPrimaryKey = this.KeyMappingStrategy.PrimaryKey;
							string childPrimaryKey = mappingStrategy.KeyMappingStrategy.PrimaryKey; // TODO: remove MappingStrategyEnum.Remote
							if (dc.ColumnName == thisPrimaryKey && childPrimaryKey != thisPrimaryKey)
								relationalColumns[i++] = new SelectExpression(thisPrimaryKey, new ExpressionName("", "[" + childPrimaryKey + "]"));
							else
								relationalColumns[i++] = new SelectExpression(dc.ColumnName, new ExpressionName("", dc.ColumnName));
						}
						RelationalQuery exp = mappingStrategy.AllInstancesQuery.Rename(childData.EntityType.Name).Project(relationalColumns);  // TODO: remove MappingStrategyEnum.Remote
						exp.Parent = relationalQuery;
						// TODO: IRelationalQuery exp = childInfo.RelationalQuery.Project(dataColumns);
						if (this.relationalQuery == null)
							this.relationalQuery = exp;
						else
						{
							DataColumn[] dataColumns2 = new DataColumn[this.entityFactory.DataColumns.Count];
							int i2 = 0;
							foreach(DataColumn dc in this.entityFactory.DataColumns)
								dataColumns2[i2++] = new DataColumn(dc.ColumnName);
							this.relationalQuery = new UnionQuery(this.relationalQuery, exp, dataColumns2);
						}
					}
				//}
					return this.relationalQuery; 
				}
			}

			public virtual ICollection<RelationalQuery> AllInstancesQueryArray 
			{ 
				get
				{
                    List<RelationalQuery> alQuery = new List<RelationalQuery>();
					alQuery.Add(this.entityFactory.ProperInstancesQuery);
					foreach(EntityData childData in this.entityFactory.EntityData.ChildrenInfo) 
					{
						IEntityMappingStrategyProvider provider = (IEntityMappingStrategyProvider)childData.GetService(typeof(IEntityMappingStrategyProvider));
						IEntityMappingStrategy mappingStrategy = provider.GetMappingStrategy(MappingStrategyEnum.Remote2);
						alQuery.AddRange(mappingStrategy.AllInstancesQueryArray);
					}
					return alQuery;
				}
			}

			public virtual ICollection<EntityData> AllChildsArray 
			{ 
				get
				{
                    List<EntityData> alQuery = new List<EntityData>();
					alQuery.Add(this.entityFactory.EntityData);
					foreach(EntityData childData in this.entityFactory.EntityData.ChildrenInfo) 
					{
						IEntityMappingStrategyProvider provider = (IEntityMappingStrategyProvider)childData.GetService(typeof(IEntityMappingStrategyProvider));
						IEntityMappingStrategy mappingStrategy = provider.GetMappingStrategy(MappingStrategyEnum.Remote2);
						alQuery.AddRange(mappingStrategy.AllChildsArray);
					}
					return alQuery;
				}
			}

			public virtual RelationalQuery ProperInstancesQuery
			{
				get
				{
					return this.entityFactory.ProperInstancesQuery;
				}
			}

			public IKeyMappingStrategy KeyMappingStrategy
			{
				get { return (IKeyMappingStrategy)this.entityFactory.EntityData.OidData.GetService(typeof(IKeyMappingStrategy)); }
			}
		}

		public virtual void Load(ObjectContext context, string correlation, ObjectPath path, object args)
		{
			Correlation s = (Correlation)path.Correlations[correlation];
			foreach(EntityData childInfo in this.EntityData.ChildrenInfo)
			{
				IEntityLoader loader = (IEntityLoader)childInfo.GetService(typeof(IEntityLoader));
				loader.Load(context, correlation, path, args);
			}
		}

		public virtual void CompleteReference(IObjectStorage storage, string oid, string roleName, PreloadFlags flags)
		{
			//EntityData entityData = this.EntityData.OidData.GetActualEntityData(oid);
			EntityData entityData = this.EntityData.GetActualEntityData(oid);
			
            IEntityLoader loader = (IEntityLoader)entityData.GetService(typeof(IEntityLoader));
			loader.CompleteReference(storage, oid, roleName, flags);
		}

		public virtual void Load(ObjectContext context, string oid)
		{
			//EntityData entityData = this.EntityData.OidData.GetActualEntityData(oid);
			EntityData entityData = this.EntityData.GetActualEntityData(oid);
			IEntityLoader loader = (IEntityLoader)entityData.GetService(typeof(IEntityLoader));
			loader.Load(context, oid);
		}

        protected IList<DataColumn> dataColumns = new List<DataColumn>();
		private EntityMappingStrategy mappingStrategy;
	}

    public class UnionMapping<TEntity> : RelationalEntityFactory<TEntity>
	{
		public UnionMapping(EntityData entityData, Type implementationType, XmlNode metadata, XmlNamespaceManager nsmgr)
			: base(entityData, metadata, nsmgr)
	    {
			if (implementationType != null)
				throw new Exception("abstract type should not have an implementation type");
		}
	}
}
